module vlib.ev.dns.Parser ;

import tango.core.Traits;
import tango.core.Array: find ;
import tango.stdc.stdint ;
import tango.stdc.string : memcpy, memset;
import tango.stdc.stringz : fromStringz;
import vlib.util.Byte ;

version(DNS_DBG) {
	import tango.io.Stdout ;
}

enum{
	NS_QFIXEDSZ	= 4 ,
	QFIXEDSZ		= NS_QFIXEDSZ ,
	NS_RRFIXEDSZ	= 10  ,
	RRFIXEDSZ	= NS_RRFIXEDSZ ,
}
enum  {
    ns_c_invalid = 0,       /* Cookie. */
    ns_c_in = 1,            /* Internet. */
    ns_c_2 = 2,             /* unallocated/unsupported. */
    ns_c_chaos = 3,         /* MIT Chaos-net. */
    ns_c_hs = 4,            /* MIT Hesiod. */
    /* Query class values which do not appear in resource records */
    ns_c_none = 254,        /* for prereq. sections in update requests */
    ns_c_any = 255,         /* Wildcard match. */
    ns_c_max = 65536
}

enum  {
    ns_t_invalid = 0,       /* Cookie. */
    ns_t_a = 1,             /* Host address. */
    ns_t_ns = 2,            /* Authoritative server. */
    ns_t_md = 3,            /* Mail destination. */
    ns_t_mf = 4,            /* Mail forwarder. */
    ns_t_cname = 5,         /* Canonical name. */
    ns_t_soa = 6,           /* Start of authority zone. */
    ns_t_mb = 7,            /* Mailbox domain name. */
    ns_t_mg = 8,            /* Mail group member. */
    ns_t_mr = 9,            /* Mail rename name. */
    ns_t_null = 10,         /* Null resource record. */
    ns_t_wks = 11,          /* Well known service. */
    ns_t_ptr = 12,          /* Domain name pointer. */
    ns_t_hinfo = 13,        /* Host information. */
    ns_t_minfo = 14,        /* Mailbox information. */
    ns_t_mx = 15,           /* Mail routing information. */
    ns_t_txt = 16,          /* Text strings. */
    ns_t_rp = 17,           /* Responsible person. */
    ns_t_afsdb = 18,        /* AFS cell database. */
    ns_t_x25 = 19,          /* X_25 calling address. */
    ns_t_isdn = 20,         /* ISDN calling address. */
    ns_t_rt = 21,           /* Router. */
    ns_t_nsap = 22,         /* NSAP address. */
    ns_t_nsap_ptr = 23,     /* Reverse NSAP lookup (deprecated). */
    ns_t_sig = 24,          /* Security signature. */
    ns_t_key = 25,          /* Security key. */
    ns_t_px = 26,           /* X.400 mail mapping. */
    ns_t_gpos = 27,         /* Geographical position (withdrawn). */
    ns_t_aaaa = 28,         /* Ip6 Address. */
    ns_t_loc = 29,          /* Location Information. */
    ns_t_nxt = 30,          /* Next domain (security). */
    ns_t_eid = 31,          /* Endpoint identifier. */
    ns_t_nimloc = 32,       /* Nimrod Locator. */
    ns_t_srv = 33,          /* Server Selection. */
    ns_t_atma = 34,         /* ATM Address */
    ns_t_naptr = 35,        /* Naming Authority PoinTeR */
    ns_t_kx = 36,           /* Key Exchange */
    ns_t_cert = 37,         /* Certification record */
    ns_t_a6 = 38,           /* IPv6 address (deprecates AAAA) */
    ns_t_dname = 39,        /* Non-terminal DNAME (for IPv6) */
    ns_t_sink = 40,         /* Kitchen sink (experimentatl) */
    ns_t_opt = 41,          /* EDNS0 option (meta-RR) */
    ns_t_apl = 42,          /* Address prefix list (RFC3123) */
    ns_t_tkey = 249,        /* Transaction key */
    ns_t_tsig = 250,        /* Transaction signature. */
    ns_t_ixfr = 251,        /* Incremental zone transfer. */
    ns_t_axfr = 252,        /* Transfer zone of authority. */
    ns_t_mailb = 253,       /* Transfer mailbox records. */
    ns_t_maila = 254,       /* Transfer mail agent records. */
    ns_t_any = 255,         /* Wildcard match. */
    ns_t_zxfr = 256,        /* BIND-specific, nonstandard. */
    ns_t_max = 65536
}

T RevertByte(T)(T v) {
	static assert(  tango.core.Traits.isIntegerType!(T) );
	T _v		= v;
	T*	p	=  cast(T*) &( (cast(ubyte*) &_v)[ 0 .. T.sizeof ] .reverse )[0] ;
	return	*p ;
}
struct DNS_HEADER {
	ushort		ID;  
	ubyte[2]		__flags ;
	//ubyte		b1; //QR    Opcode:4    AA  TC  RD  
	//ubyte		b2; //RA    Z   AD  CD  RCODE:4  
	ushort		QDCOUNT;  
	ushort		ANCOUNT;  
	ushort		NSCOUNT;  
	ushort		ARCOUNT; 
	static assert(DNS_HEADER.sizeof is 12);

	enum Mask : ubyte {  
		QR		= 0b1000_0000,  
		Opcode	= 0b0111_1000,  
		AA	= 0b0000_0100,  
		TC	= 0b0000_0010,  
		RD	= 0b0000_0001,  
		_QR	= ~ QR,
		_Opcode	= ~Opcode,
		_AA	= ~AA,
		_TC	= ~TC,
		_RD	= ~RD,
		 
		RA	= 0b1000_0000,  
		Z	= 0b0100_0000,  
		AD	= 0b0010_0000,  
		CD	= 0b0001_0000,  
		RCODE	= 0b0000_1111, 
		_RA	= ~RA,
		_Z	= ~Z,
		_AD	= ~AD,
		_CD	= ~CD,
		_RCODE	= ~RCODE,
	 }  
	   
	 bool QR(){  
	     return  cast(bool) (__flags[0]  & Mask.QR ) ;  
	 }  
	 void QR(bool v){  
	     if( v ){  
		   __flags[0]  |= Mask.QR ;  
	     }else{  
		   __flags[0]  &= Mask._QR ;
	     }  
	 }  
	 void Opcode(int v){  
	     __flags[0]  = ( __flags[0] & Mask._Opcode ) |  (v & 0x00_0f ) << 3 ;  
	 }  
	 int Opcode(){  
	     return  (__flags[0] & Mask.Opcode ) >> 3;  
	 }  
	 bool AA(){  
	     return cast(bool) (__flags[0] & Mask.AA);  
	 }  
	 void AA(bool v){  
	     if( v ){  
		   __flags[0]  |= Mask.AA ;  
	     }else{  
		   __flags[0]  &= Mask._AA;  
	     }  
	 }  
	 bool TC(){  
	     return cast(bool) (__flags[0] & Mask.TC);  
	 }  
	 void TC(bool v){  
	     if( v ){  
		   __flags[0]  |= Mask.TC ;  
	     }else{  
		   __flags[0]  &= Mask._TC ;  
	     }  
	 }  
	 bool RD(){  
	     return cast(bool) (__flags[0] & Mask.RD);  
	 }  
	 void RD(bool v){  
	     if( v ){  
		   __flags[0]  |=  Mask.RD  ;  
	     }else{  
		   __flags[0]  &= Mask._RD ;  
	     }  
	 }  
	   
	 bool RA(){  
	     return cast(bool) (__flags[1] & Mask.RA);  
	 }  
	 void RA(bool v){  
	     if( v ){  
		   __flags[1]   |= Mask.RA  ;  
	     }else{  
		   __flags[1]   &= Mask._RA;  
	     }  
	 }  
     
	 bool Z(){  
		return cast(bool) (__flags[1]  & Mask.Z);  
	 }  
	 void Z(bool v){  
		if( v ){  
			__flags[1]   |= Mask.Z ;  
		}else{  
			__flags[1]  &= Mask._Z;  
		}  
	 }  
	   
	 bool AD(){  
	     return cast(bool) (__flags[1] & Mask.AD);  
	 }  
	 void AD(bool v){  
	     if( v ){  
		   __flags[1]   |=  Mask.AD ;  
	     }else{  
		  __flags[1]   &= Mask._AD ;  
	    }  
	}  
	  
	bool CD(){  
	    return cast(bool) (__flags[1] & Mask.CD);  
	}  
	void CD(bool v){  
	    if( v ){  
		  __flags[1]   |= Mask.CD ;  
	    }else{  
		  __flags[1]   &= Mask._CD ;  
	    }  
	}  
	int RCODE(){  
	    return __flags[1]  & Mask.RCODE;  
	}  
	  
	void RCODE(int v){  
	    __flags[1]   = (__flags[1] & Mask._RCODE) | ( v & 0x00_0f );  
	}
	ushort q_count(){
		return RevertByte(QDCOUNT);
	}
	ushort ans_count(){
		return RevertByte(ANCOUNT);
	}
	ushort auth_count(){
		return RevertByte(NSCOUNT);
	}
	ushort add_count(){
		return RevertByte(ARCOUNT);
	}
	
	void q_count(ushort v){
		QDCOUNT	= RevertByte(v);
	}
	void ans_count(ushort v){
		ANCOUNT	= RevertByte(v);
	}
	void auth_count(ushort v){
		NSCOUNT	= RevertByte(v);
	}
	void add_count(ushort v){
		ARCOUNT	= RevertByte(v);
	}
}



struct DNS_STRING {
	alias typeof(*this) This ;

	ubyte*	abuf , aptr ;
	int		alen ;
	
	bool Load(ubyte* bptr, int blen, ref ubyte* _ptr ) {
		abuf	= bptr ;
		aptr	= _ptr ;
		alen	= blen ;
		int			len ;
		ubyte[1024]	tmp ;
		int 	_len		= dns_expand_name( _ptr , bptr, blen, tmp , &len);
		_ptr	+= len ;
		return _len >= 0 ;
	}
	
	char[] value(ubyte[] tmp) {
		int	len ;
		int 	_len		= dns_expand_name( aptr , abuf, alen, tmp , &len) ;
		if( _len < 0 ) {
			_len	= 0 ;
		}
		return cast(char[]) tmp[0.._len] ;
	}
}

struct DNS_QUESION {
	DNS_STRING	name ;
	ushort		type ;
	ushort		_class ;
}


struct DNS_RR_Record {
	ushort		length ;
	DNS_STRING 	data ;
}

struct DNS_A_Record {
	ubyte[4]		addr ;
}

struct DNS_AAAA_Record {
	ubyte[16]	addr ;
}

struct DNS_DN_Record {
	DNS_STRING	name;
}

struct DNS_SOA_Record {
	DNS_STRING	mname;
	DNS_STRING	rname;
	uint			serial;
	uint			refresh;
	uint			retry;
	uint			expire;
	uint			minimum;
}

struct DNS_HINFO_Record {
	DNS_STRING	cpu ;
	DNS_STRING	os ;
}

struct DNS_MINFO_Record {
	DNS_STRING	rmailbx;
	DNS_STRING	emailbx;
}

struct DNS_MX_Record {
	ushort		preference ;
	DNS_STRING	name ;
}


struct DNS_TXT_Record {
	DNS_STRING	string_count ;
	DNS_STRING[]	strings ;
}

struct DNS_WKS_Record {
	ubyte[4]		addr;
	ubyte		protocol;
	uint			maplength;
	ubyte*		map;
}

struct DNS_RP_Record {
	DNS_STRING	mailbox;
	DNS_STRING	txtdname;
}

struct DNS_AFSDB_Record{
	uint			subtype;
	DNS_STRING	hostname;
}

struct DNS_X25_Record {
	DNS_STRING	psdn_address;
} 

struct DNS_ISDN_Record{
	DNS_STRING	isdn_address;
	DNS_STRING	subaddress;
} 

struct DNS_RT_Record {
	ushort	preference;
	DNS_STRING	intermediate;
} 

struct DNS_LOC_Record {
	ubyte	__version ;
	ubyte	size;
	ubyte	horizontal_precision;
	ubyte	vertical_precision;
	uint		latitude;
	uint		longitude;
	uint		altitude;
}

struct DNS_SRV_Record {
	ushort		priority;
	ushort		weight;
	ushort		port;
	DNS_STRING	target;
}


struct DNS_RV {
	union {
		DNS_A_Record			A;
		
		/*
		DNS_AAAA_Record		AAAA;
		DNS_RR_Record			DNSNULL ;
		*/

		DNS_DN_Record		NS;
		DNS_DN_Record		MD;
		DNS_DN_Record		MF;
		DNS_DN_Record		CNAME;
		DNS_DN_Record		MB;
		DNS_DN_Record		MG;
		DNS_DN_Record		MR;
		DNS_DN_Record		PTR;
		
		DNS_MX_Record		MX;
		
		/*
		DNS_SOA_Record		SOA;
		
		DNS_WKS_Record		WKS;
		DNS_HINFO_Record		HINFO;
		DNS_MINFO_Record		MINFO;
		DNS_TXT_Record		TXT;
		DNS_RP_Record			RP;
		DNS_AFSDB_Record		AFSDB;
		DNS_X25_Record		X25;
		DNS_ISDN_Record		ISDN;
		DNS_RT_Record			RT;
		DNS_LOC_Record		LOC;
		DNS_SRV_Record		SRV;
		*/
	}
}

struct DNS_RR {
	DNS_STRING	name	;
	ushort		type		;
	ushort		_class	;
	uint			ttl		;
	ushort		len 		;
	DNS_RV		value 	;
}

struct DNS_Query {
	enum Errno {
		OK ,
		NoHeader ,
		NoNumber ,
		NoArray ,
		NoString ,
		NoA ,
		NoAA ,
		NoMX ,
		NoNS ,
		NoRR ,
	}
	
	DNS_HEADER		h ;
	DNS_QUESION[8]	q ;
	DNS_RR[8]		an, ns, ar ;
	
	private {
		ubyte*	bptr ;
		int		blen ;
		int		bpos ;
		Errno	_errno ;
	}
	
	static Errno Load(T)(T* p , ubyte* bptr, int blen, ref int bpos) {
		ubyte*	_ptr		= bptr + bpos  ;
		ubyte* 	_rptr		= bptr + blen ;
		// static const _TName	= T.stringof ;
		foreach( int i, f; (*p).tupleof ) {
			alias typeof(f) F ;
			// static const _FName	= T.tupleof[i].stringof[ _TName.length + 3 .. $ ] ;
			static if(  tango.core.Traits.isIntegerType!(F) ) {
				F t	=  *( cast(F*) _ptr ) ;
				(*p).tupleof[i]	= RevertByte!(F)(t)  ;
				_ptr	+=	F.sizeof ;
				if( _ptr > _rptr ) {
					return Errno.NoNumber ;
				}
			} else static if( isStaticArrayType!(F) ) {
				alias BaseTypeOfArrays!(F)	__f ;
				const __len	= staticArraySize!(F) * __f.sizeof ;
				memcpy( &(*p).tupleof[i][0] , _ptr, __len);
				_ptr		+=	__len ;
				if( _ptr > _rptr ) {
					return Errno.NoArray ;
				}
			} else static if( is( F == DNS_STRING ) ) {
				bool ret	= (*p).tupleof[i].Load(bptr, blen, _ptr ) ;
				if( !ret || _ptr > _rptr ) {
					return Errno.NoString ;
				}
			}  else static if( is( F == DNS_RV ) ) {
				static assert( is( T == DNS_RR )) ;
				int _pos	= _ptr - bptr  ;
				int __pos	= _pos ;
				switch(   p.type ) {
					case ns_t_a:
						Load( &p.value.A, bptr, blen, _pos );
						if( _ptr > _rptr ) {
							return Errno.NoA ;
						}
						break;
					case ns_t_ns:
						Load( &p.value.NS, bptr, blen, _pos );
						if( _ptr > _rptr ) {
							return Errno.NoNS ;
						}
						break;
					case ns_t_cname:
						Load( &p.value.CNAME, bptr, blen, _pos );
						if( _ptr > _rptr ) {
							return Errno.NoA ;
						}
						break;
					case ns_t_mx :
						Load( &p.value.MX, bptr, blen, _pos) ;
						if( _ptr > _rptr ) {
							return Errno.NoMX ;
						}
						break;
					default:
						return Errno.NoRR ;
				}
				int _len	= _pos - __pos ;
				assert(  _len is p.len ) ;
				_ptr		+= _len ;
				if( _ptr > _rptr ) {
					return Errno.NoRR ;
				}
			} else {
				static assert(false) ;
			}
			assert( _ptr - bptr <= blen );
		}
		bpos	= _ptr - bptr ;
		return Errno.OK ;
	}
	
	bool load(ubyte[] tmp){
		if( tmp.length < DNS_HEADER.sizeof ) {
			_errno	= Errno.NoHeader ;
			return false ;
		}
		bptr		= &tmp[ 0 ] ;
		blen		= tmp.length ;
		bpos		= 0 ;
		Errno	ret ;
		ret		= Load(&h, bptr, blen, bpos) ;
		if( ret !is Errno.OK ) {
			_errno	= Errno.NoHeader ;
			return false ;
		}
		assert(bpos is h.sizeof ) ;
		
		DNS_QUESION	drop_q ;
		DNS_RR	drop_rr ;
		
		int len ;
		len	=  h.QDCOUNT ;
		for( int i =0 ; i < len ; i++ ) {
			if( len >= q.length ) {
				ret	= Load(&drop_q, bptr, blen, bpos) ;
			} else {
				ret	= Load(&q[i], bptr, blen, bpos) ;
			}
			if( ret !is Errno.OK ) {
				_errno	= ret ;
				return false ;
			}
		}
		
		len	=  h.ANCOUNT ;
		for( int i =0 ; i < len ; i++ ) {
			if( i >= ns.length ) {
				ret	= Load(&drop_rr, bptr, blen, bpos) ;
			} else {
				ret	= Load(&an[i], bptr, blen, bpos) ;
			}
			if( ret !is Errno.OK ) {
				_errno	= ret ;
				return false ;
			}
		}
		
		len	=  h.NSCOUNT ;
		for( int i =0 ; i < len ; i++ ) {
			if( i >= ns.length ) {
				ret	= Load(&drop_rr, bptr, blen, bpos) ;
			} else {
				ret	= Load(&ns[i], bptr, blen, bpos) ;
			}
			if( ret !is Errno.OK ) {
				_errno	= ret ;
				return false ;
			}
		}
		
		len	=  h.ARCOUNT ;
		for( int i =0 ; i < len ; i++ ) {
			if( len >= ar.length ) {
				ret	= Load(&drop_rr, bptr, blen, bpos) ;
			} else {
				ret	= Load(&ar[i], bptr, blen, bpos) ;
			}
			if( ret !is Errno.OK ) {
				_errno	= ret ;
				return false ;
			}
		}
		
	
		if(  h.QDCOUNT > q.length ) {
			h.QDCOUNT	= q.length ;
		}
		if( h.ANCOUNT > an.length ) {
			h.ANCOUNT	= an.length ;
		}
		if( h.NSCOUNT > ar.length ) {
			h.NSCOUNT 	= ns.length ;
		}
		if( h.ARCOUNT  > ns.length ) {
			h.ARCOUNT	= ar.length ;
		}
		return true ;
	}
	
	version(DNS_DBG) {
		static void Dump(T)(T* p, char[] pre = "" ){
			static const _TName	= T.stringof ;
			Stdout.formatln("{}{}", pre, _TName);
			foreach( int i, f; (*p).tupleof ) {
				alias typeof(f) F ;
				static const _FName	= T.tupleof[i].stringof[ _TName.length + 3 .. $ ] ;
				static if( is(F==DNS_STRING) ){
					ubyte[1500] tmp ;
					Stdout.formatln("	{}{}	= {}", pre, _FName, (*p).tupleof[i].value(tmp) );
				} else static if( is(F==DNS_RV) ){
					switch( p.type ){
						case ns_t_a:
							Dump( &p.value.A, "\t" ~ pre );
							break;
						case ns_t_ns:
							Dump( &p.value.NS, "\t" ~ pre  );
							break;
						case ns_t_cname:
							Dump( &p.value.CNAME, "\t" ~ pre );
							break;
						case ns_t_mx :
							Dump( &p.value.MX, "\t" ~ pre ) ;
							break;
						default:
							Stdout.formatln("	unknow:{}", pre, p.type );
					}
				} else {
					Stdout.formatln("	{}{}	= {}", pre, _FName, (*p).tupleof[i] );
				}
			}
		}
		
		void dump() {
			Dump(&h) ;
			int len ;
			
			Stdout.formatln("Q");
			len	=  h.QDCOUNT ;
			for( int i =0 ; i < len ; i++ ) {
				Dump(&q[i], "\t") ;
			}
			
			Stdout.formatln("AN");
			len	=  h.ANCOUNT ;
			for( int i =0 ; i < len ; i++ ) {
				Dump(&an[i], "\t") ;
			}
			
			Stdout.formatln("NS");
			len	=  h.NSCOUNT ;
			for( int i =0 ; i < len ; i++ ) {
				Dump(&ns[i], "\t") ;
			}
			
			Stdout.formatln("AR");
			len	=  h.ARCOUNT ;
			for( int i =0 ; i < len ; i++ ) {
				Dump(&ar[i], "\t") ;
			}
		}
	}
	
	static int Format(char[] host, ubyte[] buf) {
		assert(buf.length > host.length);
		ubyte* ptr	= &buf[0];
		int len;
		while(true) {
			int i	= find(host, '.') ;
			if( i == host.length ) {
				buf[0]	= i;
				buf[1..i+1]	= cast(ubyte[]) host;
				len	= &buf[i+2] - ptr;
				ptr[len-1] = 0;
				break;
			}
			buf[0]	= i;
			buf[1..i+1]	= cast(ubyte[]) host[0..i];
			host		= host[i+1..$];
			buf		= buf[i+1..$];
		}
		return len;
	}
	
	ubyte[] Query_A(ubyte[1500] tmp, char[] domain, int id) {
		DNS_HEADER* dns	= cast(DNS_HEADER*) &tmp[0];
		dns.ID			= htons(id);
		dns.RD			= true;
		dns.QDCOUNT		= htons(1);
		int offset			= DNS_HEADER.sizeof  + Format(domain , tmp[ DNS_HEADER.sizeof .. $ ]) ;
		DNS_QUESION* q	= cast(DNS_QUESION*) (&tmp[ offset ] - DNS_QUESION.init.type.offsetof) ;
		q.type 			= htons( ns_t_a );  //reverse DNS : adresse -> nom
		q._class 			= htons(ns_c_in);
		offset	+=	4 ;
		return tmp[0..offset] ;
	}
	
	Errno errno(){
		return _errno ;
	}
}


package {
	static const INDIR_MASK = 0xc0 ;
	static int dns_expand_name(ubyte *encoded, ubyte* abuf, int alen, ubyte[] tmp, int* enclen) {
		int len, indir = 0 ;
		ubyte*	p ;
		struct NLen {
			union {
				int	sig;
				uint	uns;
			}
		}
		NLen	nlen;

		nlen.sig = dns_name_length(encoded, abuf, alen);
		if (nlen.sig < 0) {
			return -1 ;
		}
		if( nlen.sig >= tmp.length ) {
			return -2 ;
		}
		ubyte*	q	= &tmp[0];

		if (nlen.uns == 0) {
			/* RFC2181 says this should be ".": the root of the DNS tree.
			* Since this function strips trailing dots though, it becomes ""
			*/
			q[0] = '\0' ;
			*enclen	= 1 ; /* the caller should move one byte to get past this */
			return 0 ;
		}

		/* No error-checking necessary; it was all done by name_length(). */
		p = encoded;
		while (*p) {
			if ((*p & INDIR_MASK) == INDIR_MASK) {
				if (!indir) {
					*enclen = p + 2 - encoded;
					indir = 1;
				}
				p = abuf + ((*p & ~INDIR_MASK) << 8 | *(p + 1)) ;
			} else {
				len = *p;
				p++;
				while (len--) {
					if (*p == '.' || *p == '\\')
						*q++ = '\\';
					*q++ = *p;
					p++;
				}
				*q++ = '.';
			}
		}
		if (!indir) {
			*enclen = p + 1 - encoded ;
		}
		if( q is tmp.ptr ) {
			return 0 ;
		}
		return q - tmp.ptr -1 ;
	}


	/* Return the length of the expansion of an encoded domain name, or
	 * -1 if the encoding is invalid.
	 */
	static int dns_name_length(ubyte* encoded, ubyte* abuf, int alen) {
		int n = 0, offset, indir = 0;

		/* Allow the caller to pass us abuf + alen and have us check for it. */
		if (encoded == abuf + alen)
			return -1;

		while (*encoded) {
		      if ((*encoded & INDIR_MASK) == INDIR_MASK)
			{
			  /* Check the offset and go there. */
			  if (encoded + 1 >= abuf + alen)
			    return -1;
			  offset = (*encoded & ~INDIR_MASK) << 8 | *(encoded + 1);
			  if (offset >= alen)
			    return -1;
			  encoded = abuf + offset;

			  /* If we've seen more indirects than the message length,
			   * then there's a loop.
			   */
			  if (++indir > alen)
			    return -1;
			} else {
			  offset = *encoded;
			  if (encoded + offset + 1 >= abuf + alen)
			    return -1;
			  encoded++;
			  while (offset--)
			    {
			      n += (*encoded == '.' || *encoded == '\\') ? 2 : 1;
			      encoded++;
			    }
			  n++;
			}
		    }

		/* If there were any labels at all, then the number of dots is one
		  * less than the number of labels, so subtract one.
		  */
		return (n) ? n - 1 : n;
	}
}
